package com.uwlaser.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.uwlaser.domain.*;
import com.uwlaser.modules.system.domain.User;
import com.uwlaser.modules.system.service.dto.RoleSmallDto;
import com.uwlaser.modules.system.service.mapper.RoleMapper;
import com.uwlaser.modules.system.service.mapper.UserMapper;
import com.uwlaser.service.DeviceService;
import com.uwlaser.service.dto.DeviceAlarmParam;
import com.uwlaser.service.dto.DeviceDownParam;
import com.uwlaser.service.dto.DeviceHeartBeatParam;
import com.uwlaser.service.dto.DeviceStatusParam;
import com.uwlaser.service.mapper.*;
import com.uwlaser.utils.CacheKey;
import com.uwlaser.utils.RedisUtils;
import com.uwlaser.utils.enums.DictValueEnum;
import com.uwlaser.service.dto.*;
import com.uwlaser.service.mapper.DeviceMainMapper;
import com.uwlaser.service.mapper.DeviceStatusMapper;
import com.uwlaser.service.mapper.FactoryModelMapper;
import com.uwlaser.utils.ConvertUtil;
import lombok.AllArgsConstructor;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.util.*;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;


/**
* @author zk-Qin
* @date 2022-11-03
*/
@Service
@AllArgsConstructor
@Transactional(propagation = Propagation.SUPPORTS, readOnly = true, rollbackFor = Exception.class)
public class DeviceServiceImpl implements DeviceService {

    //设备状态
    private final DeviceStatusMapper deviceStatusMapper;
    //设备主表
    private final DeviceMainMapper deviceMainMapper;
    //设备报警
    private final DeviceAlarmMapper deviceAlarmMapper;
    //设备心跳
    private final DeviceOnlineMapper deviceOnlineMapper;
    //设备长停机
    private final DeviceStopMapper deviceStopMapper;

    private final RedisUtils redisUtils;

    private final UserMapper userMapper;

    private final RoleMapper roleMapper;

    private final FactoryModelMapper factoryModelMapper;

    /**
     * 设备报警
     * @param requestParam
     */
    @Override
    public void deviceAlarm(RequestParam<DeviceAlarmParam> requestParam) {
        DeviceAlarmParam param = requestParam.getParam();

        Date date = new Date();
        //发生报警
        if (param.getAlarmStatus().equals(DictValueEnum.ALARM_STATUS_Y.getValue())){
            DeviceAlarm deviceAlarm = new DeviceAlarm();
            deviceAlarm.setAlarmCode(param.getAlarmCode());
            deviceAlarm.setAlarmMsg(param.getAlarmMsg());
            deviceAlarm.setAlarmStatus(param.getAlarmStatus());

            deviceAlarm.setDeviceCode(requestParam.getDeviceMain().getDeviceCode());
            deviceAlarm.setDeviceName(requestParam.getDeviceMain().getDeviceName());
            deviceAlarm.setLineCode(requestParam.getFactoryLine().getModelCode());
            deviceAlarm.setSectionCode(requestParam.getFactorySection().getModelCode());
            deviceAlarm.setStationCode(requestParam.getFactoryStation().getModelCode());

            //开始报警时间
            deviceAlarm.setStartTime(date);

            deviceAlarm.setTripTime(new BigDecimal(0));

            deviceAlarm.setCreateTime(date);

            deviceAlarm.setCreateBy(param.getOperator());

            deviceAlarmMapper.insert(deviceAlarm);
        } else {
        //解除报警

            //查询报警
            DeviceAlarm alarmInfo = deviceAlarmMapper.selectOne(new LambdaQueryWrapper<DeviceAlarm>()
                    .eq(DeviceAlarm::getDeviceCode, requestParam.getDeviceMain().getDeviceCode())
                    .eq(DeviceAlarm::getLineCode, requestParam.getFactoryLine().getModelCode())
                    .eq(DeviceAlarm::getSectionCode, requestParam.getFactorySection().getModelCode())
                    .eq(DeviceAlarm::getStationCode, requestParam.getFactoryStation().getModelCode())
                    .eq(DeviceAlarm::getAlarmStatus, DictValueEnum.ALARM_STATUS_Y.getValue())
                    .eq(DeviceAlarm::getAlarmCode, param.getAlarmCode())
                    .orderByDesc(DeviceAlarm::getCreateTime)
                    .last("limit 1")
            );

            if(alarmInfo != null){
                //设置更新内容
                DeviceAlarm deviceAlarm = new DeviceAlarm();
                deviceAlarm.setAlarmStatus(DictValueEnum.ALARM_STATUS_N.getValue());
                deviceAlarm.setId(alarmInfo.getId());
                deviceAlarm.setEndTime(date);
                deviceAlarm.setUpdateTime(date);
                deviceAlarm.setUpdateBy(param.getOperator());

                //计算报警耗时
                Date startTime = alarmInfo.getStartTime();
                long tripTime = (date.getTime() - startTime.getTime()) /1000/60 ;
                deviceAlarm.setTripTime(new BigDecimal(tripTime));

                //更新时间
                deviceAlarmMapper.updateById(deviceAlarm);
            } else {
                throw new RuntimeException("更新报警记录失败：设备【"+requestParam.getDeviceMain().getDeviceCode()+"】报警代码【"+param.getAlarmCode()+"】无法找到正在报警的记录！");
            }
        }

    }

    /**
     * 设备停机
     * @param requestParam
     */
    @Override
    public void deviceDown(RequestParam<DeviceDownParam> requestParam) {
        DeviceDownParam param = requestParam.getParam();
        Date date = new Date();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date beginTime,endTime;
        try {
            beginTime = sdf.parse(param.getBeginTime());
            endTime = sdf.parse(param.getEndTime());
        } catch (Exception e) {
            beginTime = date;
            endTime = date;
        }
        DeviceStop deviceStop = new DeviceStop();
        deviceStop.setDeviceCode(requestParam.getDeviceMain().getDeviceCode());
        deviceStop.setDeviceName(requestParam.getDeviceMain().getDeviceName());
        deviceStop.setLineCode(requestParam.getFactoryLine().getModelCode());
        deviceStop.setSectionCode(requestParam.getFactorySection().getModelCode());
        deviceStop.setStationCode(requestParam.getFactoryStation().getModelCode());

        deviceStop.setDownReasonCode(param.getDownReasonCode());
        deviceStop.setBeginTime(beginTime);
        deviceStop.setEndTime(endTime);

        deviceStop.setCreateBy(param.getOperator());
        deviceStop.setCreateTime(date);

        long tripTime = (endTime.getTime() - beginTime.getTime()) /1000/60 ;
        deviceStop.setTripTime(new BigDecimal(tripTime));
        deviceStopMapper.insert(deviceStop);
    }

    /**
     * 设备状态
     * @param requestParam
     */
    @Override
    public void deviceStatus(RequestParam<DeviceStatusParam> requestParam) {
        DeviceStatusParam param = requestParam.getParam();

        //获取上一个状态信息
        DeviceStatus upDeviceStatus = deviceStatusMapper.selectOne(new LambdaQueryWrapper<DeviceStatus>()
                .eq(DeviceStatus::getDeviceCode, requestParam.getDeviceMain().getDeviceCode())
                .eq(DeviceStatus::getLineCode, requestParam.getFactoryLine().getModelCode())
                .eq(DeviceStatus::getSectionCode, requestParam.getFactorySection().getModelCode())
                .eq(DeviceStatus::getStationCode, requestParam.getFactoryStation().getModelCode())
                .orderByDesc(DeviceStatus::getCreateTime)
                .last("limit 1"));

        Date dateTime = new Date();

        if(upDeviceStatus != null){
            //计算上一个状态的耗时
            Date createTime = upDeviceStatus.getCreateTime();
            long tripTime = (dateTime.getTime() - createTime.getTime()) /1000/60 ;

            DeviceStatus deviceStatus = new DeviceStatus();
            deviceStatus.setId(upDeviceStatus.getId());
            deviceStatus.setTripTime(new BigDecimal(tripTime));
            //更新上一个状态的耗时
            deviceStatusMapper.updateById(deviceStatus);
        }


        //添加当前状态记录
        DeviceStatus deviceStatus = new DeviceStatus();
        deviceStatus.setDeviceStatus(param.getDeviceStatus());
        deviceStatus.setDeviceCode(requestParam.getDeviceMain().getDeviceCode());
        deviceStatus.setDeviceName(requestParam.getDeviceMain().getDeviceName());

        deviceStatus.setLineCode(requestParam.getFactoryLine().getModelCode());
        deviceStatus.setSectionCode(requestParam.getFactorySection().getModelCode());
        deviceStatus.setStationCode(requestParam.getFactoryStation().getModelCode());
        deviceStatus.setCreateBy(requestParam.getOperator());
        deviceStatus.setCreateTime(dateTime);
        deviceStatus.setTripTime(new BigDecimal(0));

        deviceStatusMapper.insert(deviceStatus);

        //把当前状态更新到主表
        DeviceMain deviceMain = new DeviceMain();
        deviceMain.setDeviceStatus(param.getDeviceStatus());

        deviceMainMapper.update(deviceMain,new LambdaQueryWrapper<DeviceMain>()
                .eq(DeviceMain :: getDeviceCode,param.getDeviceCode())
                .eq(DeviceMain :: getLineCode,requestParam.getFactoryLine().getModelCode())
                .eq(DeviceMain :: getSectionCode,requestParam.getFactorySection().getModelCode())
                .eq(DeviceMain :: getStationCode,requestParam.getFactoryStation().getModelCode()));
    }

    /**
     * 设备心跳
     * @param requestParam
     */
    @Override
    public void deviceHeartBeat(RequestParam<DeviceHeartBeatParam> requestParam) {

        DeviceHeartBeatParam param = requestParam.getParam();
        Map<String, DeviceOnline> map = null;
        //从缓存获取
        Object deviceOnline = redisUtils.get(CacheKey.DEVICE_ON_LINE);
        if(deviceOnline == null){
            map = new ConcurrentHashMap<>();
            //初始化Redis缓存
            initAllOnline(map);
        } else {
            map = (Map<String, DeviceOnline>) deviceOnline;
        }

        DeviceOnline online = map.get(param.getDeviceCode());
        //如果状态不相同则插入数据库
        if(online == null || !online.getIsOnline().equals(param.getIsOnline())){
            //插入在线记录
            DeviceOnline addOnline = new DeviceOnline();
            addOnline.setDeviceCode(requestParam.getDeviceMain().getDeviceCode());
            addOnline.setDeviceName(requestParam.getDeviceMain().getDeviceName());
            addOnline.setLineCode(requestParam.getFactoryLine().getModelCode());
            addOnline.setSectionCode(requestParam.getFactorySection().getModelCode());
            addOnline.setStationCode(requestParam.getFactoryStation().getModelCode());
            addOnline.setCreateBy(param.getOperator());
            addOnline.setCreateTime(new Date());
            addOnline.setIsOnline(param.getIsOnline());
            deviceOnlineMapper.insert(addOnline);

            //把当前状态更新到主表
            DeviceMain deviceMain = new DeviceMain();
            deviceMain.setIsOnline(param.getIsOnline());

            deviceMainMapper.update(deviceMain,new LambdaQueryWrapper<DeviceMain>()
                    .eq(DeviceMain :: getDeviceCode,requestParam.getDeviceMain().getDeviceCode())
                    .eq(DeviceMain :: getLineCode,requestParam.getFactoryLine().getModelCode())
                    .eq(DeviceMain :: getSectionCode,requestParam.getFactorySection().getModelCode())
                    .eq(DeviceMain :: getStationCode,requestParam.getFactoryStation().getModelCode()));

            online = addOnline;
        } else {
            online.setCreateTime(new Date());
        }
        map.put(param.getDeviceCode(),online);
        redisUtils.set(CacheKey.DEVICE_ON_LINE,map);
    }

    /**
     * 定时更新设备在线状态
     */
    @Override
    public void updateoOnline() {

        //从缓存获取
        Object deviceOnline = redisUtils.get(CacheKey.DEVICE_ON_LINE);

        Map<String, DeviceOnline> map = null;

        if(deviceOnline == null){
            map = new ConcurrentHashMap<>();
            //初始化Redis缓存
            initAllOnline(map);
        } else {
            map = (Map<String, DeviceOnline>) deviceOnline;
            Date date = new Date();
            for (String deviceCode : map.keySet()) {
                DeviceOnline online = map.get(deviceCode);
                if(online.getIsOnline().equals(DictValueEnum.DEVICE_ON_LINE.getValue())){
                    Date createTime = online.getCreateTime();
                    long tripTime = (date.getTime() - createTime.getTime()) /1000 ;
                    //如果在线的设备30秒内没有心跳则判断为离线
                    if(tripTime > 30){
                        //插入在线记录
                        DeviceOnline addOnline = new DeviceOnline();
                        addOnline.setDeviceCode(online.getDeviceCode());
                        addOnline.setDeviceName(online.getDeviceName());
                        addOnline.setLineCode(online.getLineCode());
                        addOnline.setSectionCode(online.getSectionCode());
                        addOnline.setStationCode(online.getStationCode());
                        addOnline.setCreateBy("system");
                        addOnline.setCreateTime(date);
                        addOnline.setIsOnline(DictValueEnum.DEVICE_OFF_LINE.getValue());//离线
                        deviceOnlineMapper.insert(addOnline);

                        //把当前状态更新到主表
                        DeviceMain deviceMain = new DeviceMain();
                        deviceMain.setIsOnline(DictValueEnum.DEVICE_OFF_LINE.getValue());

                        deviceMainMapper.update(deviceMain,new LambdaQueryWrapper<DeviceMain>()
                                .eq(DeviceMain :: getDeviceCode,online.getDeviceCode())
                                .eq(DeviceMain :: getLineCode,online.getLineCode())
                                .eq(DeviceMain :: getSectionCode,online.getSectionCode())
                                .eq(DeviceMain :: getStationCode,online.getStationCode()));

                        //把当前状态更新到Redis
                        online.setIsOnline(DictValueEnum.DEVICE_OFF_LINE.getValue());
                        online.setCreateTime(date);
                        map.put(deviceCode,online);
                        //最终更新Redis
                        redisUtils.set(CacheKey.DEVICE_ON_LINE,map);
                    }
                }
            }
        }
    }

    /**
     * 根据设备编号查询设备状态
     * @return
     */
    private DeviceOnline getOnline(String deviceCode,List<DeviceOnline> deviceOnlines){

        DeviceOnline deviceOnline = new DeviceOnline();
        deviceOnline.setDeviceCode(deviceCode);
        deviceOnline.setIsOnline(DictValueEnum.DEVICE_OFF_LINE.getValue());
        deviceOnline.setCreateTime(new Date());

        if(deviceOnlines == null || deviceOnlines.size() == 0){

            return deviceOnline;
        }

        for (DeviceOnline online : deviceOnlines) {
            if(online.getDeviceCode().equals(deviceCode)) {
                return online;
            }
        }

        return deviceOnline;
    }

    /**
     * 初始化redis缓存
     * @param map
     */
    public void initAllOnline(Map<String, DeviceOnline> map){
        //设备列表
        List<DeviceMain> deviceMains = deviceMainMapper.selectList(null);
        if(deviceMains == null || deviceMains.size() == 0){
            return;
        }
        //设备编码
        List<String> deviceCodes = deviceMains.stream().map(DeviceMain::getDeviceCode).collect(Collectors.toList());

        //根据设备编号获取最新的在线状态记录
        List<DeviceOnline> deviceOnlines = deviceOnlineMapper.getMaxDeviceOnlineByDeviceCodes(deviceCodes);

        //把设备最新在线状态设置到Redis中
        for (String deviceCode : deviceCodes) {
            map.put(deviceCode,getOnline(deviceCode, deviceOnlines));
        }
        //最终更新Redis
        redisUtils.set(CacheKey.DEVICE_ON_LINE,map);
    }

    /**
     * 设备登录
     * @param requestParam
     * @return
     */
    @Override
    public DeviceLoginResult getDeviceLogin(RequestParam<DeviceLoginParam> requestParam) {
        DeviceLoginParam deviceLoginParam = requestParam.getParam();
        // 返回参数
        DeviceLoginResult result = new DeviceLoginResult();
        PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
        // 根据用户名查询
        User user = userMapper.lambdaQuery().eq(User::getUsername, deviceLoginParam.getUserName()).one();
        if(user == null){
            throw new RuntimeException("登录账号错误！");
        }
        if(!passwordEncoder.matches(deviceLoginParam.getPassword(), user.getPassword())){
            throw new RuntimeException("登录密码错误！");
        }
        List<String> roles = new ArrayList<>();
        List<FactoryModelDto> factoryModel = new ArrayList<>();
        // 查询角色设备的关系
        ConvertUtil.convertSet(roleMapper.selectLink(user.getId()), RoleSmallDto.class).forEach( roleSmallDto -> {
            ConvertUtil.convertSet(factoryModelMapper.selectLink(roleSmallDto.getId(), deviceLoginParam.getDeviceCode()), FactoryModelDto.class).forEach( factoryModelDto -> {
                System.out.println("设备信息" + factoryModelDto);
                if(!Objects.equals(deviceLoginParam.getDeviceCode(), factoryModelDto.getModelCode())
                        && DictValueEnum.MODEL_TYPE_DEVICE.getValue().equals(factoryModelDto.getModelType())){
                    throw new RuntimeException("该账号没有【" + deviceLoginParam.getDeviceCode() +"】设备权限");
                }
                factoryModel.add(factoryModelDto);
            });
            roles.add(roleSmallDto.getName());
        });
        if (factoryModel.isEmpty()) {
            throw new RuntimeException("该账号没有【" + deviceLoginParam.getDeviceCode() +"】设备权限");
        }
        result.setRoles(roles);
        result.setUser(user.getUsername(), user.getNickName(), "/avatar/admin.jpg");
        return result;
    }
}

